package org.xx.armory.mbeans;

import org.xx.armory.rpc.RpcHandler;
import org.xx.armory.rpc.WorkThreadInfo;
import org.xx.armory.rpc.routing.Route;
import org.xx.armory.rpc.routing.RouteTable;

import javax.management.openmbean.CompositeData;
import javax.management.openmbean.CompositeDataSupport;
import javax.management.openmbean.CompositeType;
import javax.management.openmbean.OpenDataException;
import javax.management.openmbean.OpenType;
import javax.management.openmbean.SimpleType;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

import static org.apache.commons.lang3.StringUtils.join;
import static org.xx.armory.commons.Validators.notNull;

public class RpcHandlerProxy
        implements RpcHandlerProxyMBean {
    private static final String[] ROUTE_ITEM_NAMES = {"name", "pattern", "permissions", "verb", "target"};
    private static final String[] ROUTE_ITEM_DESCRIPTIONS = {"name", "pattern", "permissions", "verb", "target"};
    private static final OpenType<?>[] ROUTE_ITEM_TYPES = {SimpleType.STRING, SimpleType.STRING, SimpleType.STRING, SimpleType.STRING, SimpleType.STRING};

    private static final String[] THREAD_ITEM_NAMES = {"id", "name", "group", "state", "stackTrace"};
    private static final String[] THREAD_ITEM_DESCRIPTIONS = {"id", "name", "group", "state", "stackTrace"};
    private static final OpenType<?>[] THREAD_ITEM_TYPES = {SimpleType.LONG, SimpleType.STRING, SimpleType.STRING, SimpleType.STRING, SimpleType.STRING};

    private static final CompositeType THREAD_TYPE = createThreadType();

    private static final CompositeType ROUTE_TYPE = createRouteType();

    private final RpcHandler rpcHandler;

    public RpcHandlerProxy(
            RpcHandler rpcHandler
    ) {
        this.rpcHandler = notNull(rpcHandler, "rpcHandler");
    }

    private static CompositeType createRouteType() {
        try {
            return new CompositeType("Route", "Route", ROUTE_ITEM_NAMES, ROUTE_ITEM_DESCRIPTIONS, ROUTE_ITEM_TYPES);
        } catch (OpenDataException ex) {
            throw new IllegalStateException(ex);
        }
    }

    private static CompositeType createThreadType() {
        try {
            return new CompositeType("Thread", "Thread", THREAD_ITEM_NAMES, THREAD_ITEM_DESCRIPTIONS, THREAD_ITEM_TYPES);
        } catch (OpenDataException ex) {
            throw new IllegalStateException(ex);
        }
    }

    private CompositeData toComposite(
            Route route
    ) {
        if (route == null) {
            return null;
        }

        final Object[] values = {
                route.getName(), route.getPattern(), join(route.getPermissions(), ','),
                route.getVerb().toString(), route.getClassName() + "." + route.getMethodName()
        };

        try {
            return new CompositeDataSupport(ROUTE_TYPE, ROUTE_ITEM_NAMES, values);
        } catch (OpenDataException ignored) {
            return null;
        }
    }

    private CompositeData toComposite(
            WorkThreadInfo thread
    ) {
        if (thread == null) {
            return null;
        }

        final Object[] values = {
                thread.getId(), thread.getName(), thread.getGroupName(), thread.getState(), thread.getStackTrace()
        };

        try {
            return new CompositeDataSupport(THREAD_TYPE, THREAD_ITEM_NAMES, values);
        } catch (OpenDataException ignored) {
            return null;
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public List<CompositeData> getRouteTable() {
        final RouteTable routeTable = this.rpcHandler.getRouteTable();
        return routeTable.stream().map(this::toComposite).filter(Objects::nonNull).collect(Collectors.toList());
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public int getParallelism() {
        return this.rpcHandler.getParallelism();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public List<CompositeData> dumpWorkThreads() {
        return this.rpcHandler.getWorkThreads()
                              .stream()
                              .map(this::toComposite)
                              .filter(Objects::nonNull)
                              .collect(Collectors.toList());
    }
}
